<template>
  <div id="slideVerify" class="slide-verify" :style="{ width: w + 'px' }">
    <!-- 图片加载遮蔽罩 -->
    <div :class="{ 'slider-verify-loading': loadBlock }"></div>
    <canvas ref="canvas" :width="w" :height="h"></canvas>
    <div v-if="show" class="slide-verify-refresh-icon" @click="refresh">
      <i class="iconfont icon-refresh"></i>
    </div>
    <canvas ref="block" :width="w" :height="h" class="slide-verify-block"></canvas>
    <!-- container -->
    <div
      class="slide-verify-slider"
      :class="{
        'container-active': containerCls.containerActive,
        'container-success': containerCls.containerSuccess,
        'container-fail': containerCls.containerFail,
      }"
    >
      <div class="slide-verify-slider-mask" :style="{ width: sliderBox.width }">
        <!-- slider -->
        <div
          class="slide-verify-slider-mask-item"
          :style="{ left: sliderBox.left }"
          @mousedown="start"
          @touchstart="start"
          @touchmove="touchMoveEvent"
          @touchend="touchEndEvent"
        >
          <i :class="['slide-verify-slider-mask-item-icon', 'iconfont', `icon-${sliderBox.iconCls}`]"></i>
        </div>
      </div>
      <span class="slide-verify-slider-text">{{ sliderText }}</span>
    </div>
  </div>
</template>

<script lang="ts" setup>
  import { ref, reactive, onMounted, onBeforeUnmount, PropType } from 'vue';

  import useSlideAction from './hooks';
  import { createImg, draw, getRandomImg, getRandomNumberByRange, throttle } from './util';

  const { success, start, move, end, verify } = useSlideAction();

  const props = defineProps({
    // block length
    l: {
      type: Number,
      default: 42,
    },
    // block radius
    r: {
      type: Number,
      default: 10,
    },
    // canvas width
    w: {
      type: Number,
      default: 310,
    },
    // canvas height
    h: {
      type: Number,
      default: 155,
    },
    sliderText: {
      type: String,
      default: 'Slide filled right',
    },
    accuracy: {
      type: Number,
      default: 5, // 若为 -1 则不进行机器判断
    },
    show: {
      type: Boolean,
      default: true,
    },
    imgs: {
      type: Array as PropType<any[]>,
      default: () => [],
    },
    interval: {
      // 节流时长间隔
      type: Number,
      default: 50,
    },
  });

  const emit = defineEmits(['success', 'again', 'fail', 'refresh']);

  // 图片加载完关闭遮蔽罩
  const loadBlock = ref(true);
  const blockX = ref(0);
  const blockY = ref(0);
  // class
  const containerCls = reactive({
    containerActive: false, // container active class
    containerSuccess: false, // container success class
    containerFail: false, // container fail class
  });
  // sliderMaskWidth sliderLeft
  const sliderBox = reactive({
    iconCls: 'arrow-right',
    width: '0',
    left: '0',
  });

  const block = ref<HTMLCanvasElement>();
  const blockCtx = ref<CanvasRenderingContext2D | null>();
  const canvas = ref<HTMLCanvasElement>();
  const canvasCtx = ref<CanvasRenderingContext2D | null>();
  let img: HTMLImageElement;

  // event
  const reset = () => {
    success.value = false;
    containerCls.containerActive = false;
    containerCls.containerSuccess = false;
    containerCls.containerFail = false;
    sliderBox.iconCls = 'arrow-right';
    sliderBox.left = '0';
    sliderBox.width = '0';

    block.value!.style.left = '0';
    canvasCtx.value?.clearRect(0, 0, props.w, props.h);
    blockCtx.value?.clearRect(0, 0, props.w, props.h);
    block.value!.width = props.w;

    // generate img
    img.src = getRandomImg(props.imgs);
  };
  const refresh = () => {
    reset();
    emit('refresh');
  };

  function moveCb(moveX: number) {
    sliderBox.left = `${moveX}px`;
    const blockLeft = ((props.w - 40 - 20) / (props.w - 40)) * moveX;
    block.value!.style.left = `${blockLeft}px`;

    containerCls.containerActive = true;
    sliderBox.width = `${moveX}px`;
  }

  function endCb(timestamp: number) {
    const { spliced, TuringTest } = verify(block.value!.style.left, blockX.value, props.accuracy);
    if (spliced) {
      if (props.accuracy === -1) {
        containerCls.containerSuccess = true;
        sliderBox.iconCls = 'success';
        success.value = true;
        emit('success', timestamp);
        return;
      }
      if (TuringTest) {
        // success
        containerCls.containerSuccess = true;
        sliderBox.iconCls = 'success';
        success.value = true;
        emit('success', timestamp);
      } else {
        containerCls.containerFail = true;
        sliderBox.iconCls = 'fail';
        emit('again');
      }
    } else {
      containerCls.containerFail = true;
      sliderBox.iconCls = 'fail';
      emit('fail');
      setTimeout(() => {
        reset();
      }, 1000);
    }
  }

  const touchMoveEvent = throttle((e: TouchEvent | MouseEvent) => {
    move(props.w, e, moveCb);
  }, props.interval);

  const touchEndEvent = (e: TouchEvent | MouseEvent) => {
    end(e, endCb);
  };

  onMounted(() => {
    const canvasCtx0 = canvas.value?.getContext('2d');
    const blockCtx0 = block.value?.getContext('2d');
    canvasCtx.value = canvasCtx0;
    blockCtx.value = blockCtx0;

    img = createImg(props.imgs, () => {
      loadBlock.value = false;
      const ll = props.l + props.r * 2 + 3;
      // draw block
      blockX.value = getRandomNumberByRange(ll + 10, props.w - (ll + 10));
      blockY.value = getRandomNumberByRange(10 + props.r * 2, props.h - (ll + 10));
      if (canvasCtx0 && blockCtx0) {
        draw(canvasCtx0, blockX.value, blockY.value, props.l, props.r, 'fill');
        draw(blockCtx0, blockX.value, blockY.value, props.l, props.r, 'clip');
        // draw image
        canvasCtx0.drawImage(img, 0, 0, props.w, props.h);
        blockCtx0.drawImage(img, 0, 0, props.w, props.h);
        // getImage
        const y0 = blockY.value - props.r * 2 - 1;
        const imgData = blockCtx0.getImageData(blockX.value, y0, ll, ll);
        block.value!.width = ll;
        blockCtx0.putImageData(imgData, 0, y0);
      }
    });

    // bindEvent
    document.addEventListener('mousemove', touchMoveEvent);
    document.addEventListener('mouseup', touchEndEvent);
  });

  // 移除全局事件
  onBeforeUnmount(() => {
    document.removeEventListener('mousemove', touchMoveEvent);
    document.removeEventListener('mouseup', touchEndEvent);
  });
</script>

<style lang="less">
  @import url('./assets/iconfont.css');

  .position() {
    position: absolute;
    left: 0;
    top: 0;
  }
  .slide-verify {
    position: relative;
    &-loading {
      .position();
      right: 0;
      bottom: 0;
      background: rgba(255, 255, 255, 0.9);
      z-index: 999;
      animation: loading 1.5s infinite;
    }

    &-block {
      .position();
    }

    &-refresh-icon {
      position: absolute;
      right: 0;
      top: 0;
      width: 34px;
      height: 34px;
      cursor: pointer;
      .iconfont {
        font-size: 34px;
        color: #fff;
      }
    }
    &-slider {
      position: relative;
      text-align: center;
      width: 100%;
      height: 40px;
      line-height: 40px;
      //margin-top: 15px;
      background: #f7f9fa;
      color: #45494c;
      border: 1px solid #e4e7eb;
      &-mask {
        .position();
        height: 40px;
        border: 0 solid #1991fa;
        background: #d1e9fe;
        &-item {
          .position();
          width: 40px;
          height: 40px;
          background: #fff;
          box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
          cursor: pointer;
          transition: background 0.2s linear;
          display: flex;
          align-items: center;
          justify-content: center;
          &:hover {
            background: #1991fa;
            .iconfont {
              color: #fff;
            }
          }
          &-icon {
            line-height: 1;
            font-size: 30px;
            color: #303030;
          }
        }
      }
    }
  }

  .container-active .slide-verify-slider-mask {
    height: 38px;
    border-width: 1px;
    &-item {
      height: 38px;
      top: -1px;
      border: 1px solid #1991fa;
    }
  }

  .container-success .slide-verify-slider-mask {
    height: 38px;
    border: 1px solid #52ccba;
    background-color: #d2f4ef;
    &-item {
      height: 38px;
      top: -1px;
      border: 1px solid #52ccba;
      background-color: #52ccba !important;
    }
    .iconfont {
      color: #fff;
    }
  }

  .container-fail .slide-verify-slider-mask {
    height: 38px;
    border: 1px solid #f57a7a;
    background-color: #fce1e1;
    &-item {
      height: 38px;
      top: -1px;
      border: 1px solid #f57a7a;
      background-color: #f57a7a !important;
    }
    .iconfont {
      color: #fff;
    }
  }

  .container-active .slide-verify-slider-text,
  .container-success .slide-verify-slider-text,
  .container-fail .slide-verify-slider-text {
    display: none;
  }

  @keyframes loading {
    0% {
      opacity: 0.7;
    }
    100% {
      opacity: 9;
    }
  }
</style>
